package main

import (
	"notabug.org/apiote/next-eeze/agent"
	"notabug.org/apiote/next-eeze/config"
	"notabug.org/apiote/next-eeze/fido"
	"notabug.org/apiote/next-eeze/fs"
	"notabug.org/apiote/next-eeze/operation"
	"notabug.org/apiote/next-eeze/server"

	"encoding/hex"
	"fmt"
	"log"
	"os"

	"golang.org/x/crypto/ssh/terminal"

	"git.sr.ht/~sircmpwn/getopt"
)

func readMasterPassword() (string, error) {
	present, err := fs.IsFidoCredentialPresent()
	if err != nil {
		fmt.Println(err)
		return "", err
	}
	if present {
		return readMasterPasswordFido()
	} else {
		return readMasterPasswordStdin()
	}
}

func readMasterPasswordFido() (string, error) {
	c, err := fs.ReadFidoCredential()
	if err != nil {
		return "", err
	}
	// todo memguard
	secret := fido.GetHmacSecret("next-eeze", "", c.Cdh, c.Salt, c.CredID) // todo pin

	return hex.EncodeToString(secret), nil
}

func readMasterPasswordStdin() (string, error) {
	fmt.Print("Master password: ")
	// todo memguard
	masterPass_b, err := terminal.ReadPassword(int(os.Stdin.Fd()))
	if err != nil {
		return "", err
	}
	// todo memguard
	masterPassword := string(masterPass_b)
	fmt.Print("\n")
	return masterPassword, nil
}

func main() {
	C := getopt.Bool("C", false, "Config")
	S := getopt.Bool("S", false, "Sync")
	L := getopt.Bool("L", false, "List")
	G := getopt.Bool("G", false, "Get")
	P := getopt.Bool("P", false, "Put")

	var u string
	getopt.StringVar(&u, "u", "", "filter Get by username")
	var l string
	getopt.StringVar(&l, "l", "", "filter Get by label")
	var s string
	getopt.StringVar(&s, "s", "", "filter Get by url (service/server)")
	f := getopt.Bool("f", false, "show full entry in Get, instead of just username/password")
	p := getopt.Bool("p", false, "show just password in Get")
	i := getopt.Bool("i", false, "in Config: set server, username, password (initialise)")
	r := getopt.Bool("r", false, "in Config: reëncrypt (change master password)")
	fido2 := getopt.Bool("2", false, "in Config, reëncrypt: use fido2 device")
	n := getopt.Bool("n", false, "do not ask for anything, fail if password cannot be obtained from agent")
	b := getopt.Bool("b", false, "block until password can be received from agent")

	err := getopt.Parse()
	if err != nil {
		log.Println("Error parsing opts. ", err)
		return
	}

	if *P {
		masterPassword, _ := readMasterPassword()
		agent.GiveMasterPassword(masterPassword)
		return
	}

	masterPassword, err := agent.GetMasterPassword(*b)
	if masterPassword == "" && err == nil {
		agent.StartAgent()
	}
	masterPassword, err = agent.GetMasterPassword(*b)
	if err != nil {
		log.Println("Error getting from agent", err)
	}

	if masterPassword == "" && !*C && *n {
		log.Fatalln("Password needed in non-interactive mode")
	}

	if masterPassword == "" || (*C && (*i || *r)) {
		masterPassword, _ = readMasterPassword()
		agent.GiveMasterPassword(masterPassword)
	}

	if *C {
		if *i {
			config.Init(masterPassword)
		} else if *r {
			// todo memguard
			newMasterPassword, err := config.Reëncrypt(masterPassword, *fido2)
			if err != nil {
				log.Println("Error reëncrypting. ", err)
				return
			} else {
				agent.GiveMasterPassword(newMasterPassword)
			}
		}
	} else if *S {
		err = server.Sync(masterPassword)
	} else if *G {
		var r string
		r, err = operation.Get(u, l, s, *f, *p, masterPassword)
		// todo if error:wrongPass -> kill agent
		fmt.Println(r)
	} else if *L {
		var r string
		r, err = operation.List(masterPassword)
		fmt.Println(r)
	} else {
		getopt.Usage()
	}
	if err != nil {
		log.Println("Error. ", err)
		return
	}
}
